Skip to content

Centralize Swift checkout message ingress validation - #644

Merged
markmur merged 1 commit into
mainfrom
swift-message-rejection-lifecycle
Aug 19, 2026
Merged

Centralize Swift checkout message ingress validation#644
markmur merged 1 commit into
mainfrom
swift-message-rejection-lifecycle

Conversation

@markmur

@markmur markmur commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What changes are you making?

This PR centralizes Swift message admission behind an internal CheckoutMessageIngressPolicy.

Incoming WebKit messages now pass through an internal CheckoutMessageIngressPolicy before reaching the checkout protocol client:

let ingressPolicy = CheckoutMessageIngressPolicy(
    configuredOrigins: ShopifyCheckoutKit.configuration.allowedMessageOrigins,
    checkoutURL: loadedCheckoutURL
)

switch ingressPolicy.evaluate(message, in: self) {
case .accepted:
    break
case let .rejected(rejection):
    switch rejection.reason {
    case .childFrame:
        OSLogger.shared.debug("Ignoring checkout message from a child frame.")
    case .unsupportedPort, .originNotAllowed:
        OSLogger.shared.warn(
            "Rejected checkout message from \(rejection.origin): \(rejection.reason.logDescription)"
        )
    }
    return
}

// Only admitted messages continue into the existing checkout protocol handling.

The WebKit-specific overload adapts authenticated frame, origin, and request URL metadata into the policy's transport-neutral IncomingCheckoutMessage model. The core evaluator remains independently testable, while the WebView call site no longer assembles admission metadata itself.

The policy validates that metadata before a message reaches the checkout protocol client. Keeping this decision at the native WebView boundary means the protocol client can focus on handling admitted checkout protocol messages rather than transport trust.

Rejection behavior

A rejected message means that specific input was not trusted. It does not mean the checkout session failed: checkout may be displaying an external surface that also posts messages, and unrelated page activity must not be able to terminate an otherwise healthy checkout. The rejection branch selects the appropriate log level and returns before protocol processing begins.

  • Child-frame messages remain debug-only because payment and 3DS iframes can produce routine bridge traffic.
  • Origin and explicit-port-zero rejections are logged at warning level and dropped.
  • Rejected messages never reach the checkout protocol client.
  • Rejection does not transition PreloadState to .failed or evict the preload.
  • Rejection does not create a CheckoutError or call .onFail / checkoutDidFail(error:).
  • The rejection model remains internal because this approach does not expose a public diagnostics stream.

Consumer API

Consumers only configure the origins they trust. The existing lifecycle APIs remain reserved for actual checkout failures:

ShopifyCheckoutKit.configure {
  $0.allowedMessageOrigins = [
    "https://checkout.example.com",
    "https://*.example.com",
  ]
}

There is no message-rejection subscription. Rejections remain an internal signal that is logged at the appropriate level and dropped.

React Native consumer API

No React Native event or callback is added. Rejected native messages are logged and dropped; they do not dispatch onFail:

checkoutKit.present(checkoutUrl, {
  onFail(error) {
    // Called for checkout lifecycle failures, not rejected messages.
    reportCheckoutFailure(error);
  },
});

Before you merge

Important

  • I've added tests to support my implementation
  • I have read and agree with the Contribution Guidelines
  • I have read and agree with the Code of Conduct
  • I've updated the relevant platform README (platforms/swift/README.md and/or platforms/android/README.md)

@github-actions github-actions Bot added the #gsd:50662 Rebase Checkout Kit on UCP label Aug 14, 2026

markmur commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

@markmur markmur changed the title Route Swift message rejection through lifecycle failures Centralize Swift checkout message ingress validation Aug 14, 2026
@markmur
markmur force-pushed the swift-message-rejection-lifecycle branch from 63d2a15 to ac84d8f Compare August 14, 2026 11:44
@markmur
markmur marked this pull request as ready for review August 14, 2026 11:47
@markmur
markmur requested a review from a team as a code owner August 14, 2026 11:47
@bitrise

bitrise Bot commented Aug 14, 2026

Copy link
Copy Markdown

Install this build

Open Tophat, select your target device, then click Install. Links open on the Mac running Tophat.

SDK Install
Swift Install with Tophat

Checkout Kit E2E results

Status Suite Target Platform OS version tag Device
swift-ios swift ios latest iPhone 15
iOS 27 Beta

@markmur
markmur force-pushed the swift-message-rejection-lifecycle branch from ac84d8f to ee4b8b8 Compare August 18, 2026 13:02
@markmur markmur self-assigned this Aug 18, 2026
@markmur
markmur force-pushed the swift-message-rejection-lifecycle branch from ee4b8b8 to 56fd72d Compare August 18, 2026 13:25
@markmur
markmur requested a review from tiagocandido August 18, 2026 15:19
Comment thread platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebView.swift Outdated
@markmur
markmur force-pushed the swift-message-rejection-lifecycle branch from 56fd72d to 1dcc9bd Compare August 19, 2026 12:58
@markmur
markmur merged commit 6f17428 into main Aug 19, 2026
32 of 33 checks passed
@markmur
markmur deleted the swift-message-rejection-lifecycle branch August 19, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

#gsd:50662 Rebase Checkout Kit on UCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants